package com.cjyd.hrjb.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cjyd.hrjb.config.XetConfig;
import com.cjyd.hrjb.exception.ServiceException;
import com.cjyd.hrjb.mapper.AccessTokenMapper;
import com.cjyd.hrjb.model.dto.ResponseDto;
import com.cjyd.hrjb.model.dto.TokenDto;
import com.cjyd.hrjb.model.entity.AccessToken;
import com.cjyd.hrjb.service.AccessTokenService;
import com.cjyd.hrjb.utils.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Created fei wo by 2020/4/5
 */
@Slf4j
@Service
public class AccessTokenServiceImpl implements AccessTokenService {

    private static final String ACCESS_TOKEN_URL = "https://api.xiaoe-tech.com/token";
    private static final String ACCESS_TOKEN_REDIS_KEY = "xet:access:token";

    private final AccessTokenMapper accessTokenMapper;
    private final XetConfig xetConfig;
    private final StringRedisTemplate stringRedisTemplate;

    public AccessTokenServiceImpl(AccessTokenMapper accessTokenMapper, XetConfig xetConfig, StringRedisTemplate stringRedisTemplate) {
        this.accessTokenMapper = accessTokenMapper;
        this.xetConfig = xetConfig;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 同步拿到Access Token
     * @return {@link AccessToken
     */
    private AccessToken syncAccessToken() {
        TokenDto accessToken = reqAccessToken();
        if (accessToken == null) throw  new ServiceException(ResultCode.SYSTEM_ERROR);
        AccessToken token;
        Integer count = accessTokenMapper.selectCount(Wrappers.emptyWrapper());
        if (count == 0) {
            token = new AccessToken();
            token.setToken(accessToken.getAccessToken());
            token.setExpireIn(accessToken.getExpiresIn());
            token.setSaveTime(Instant.now().getEpochSecond());
            int insert = accessTokenMapper.insert(token);
            if (insert != 1) throw new ServiceException(ResultCode.UPDATE_ACCESS_TOKEN_FAIL);
        }else {
            List<AccessToken> accessTokens = accessTokenMapper.selectList(Wrappers.emptyWrapper());
            Optional<AccessToken> optionalAccessToken = accessTokens.stream().findFirst();
            optionalAccessToken.orElseThrow(() -> new ServiceException(ResultCode.UPDATE_ACCESS_TOKEN_FAIL));
            token = optionalAccessToken.get();
            token.setToken(accessToken.getAccessToken());
            token.setExpireIn(accessToken.getExpiresIn());
            token.setSaveTime(Instant.now().getEpochSecond());
            int update = accessTokenMapper.updateById(token);
            if (update != 1) throw new ServiceException(ResultCode.UPDATE_ACCESS_TOKEN_FAIL);
        }
        stringRedisTemplate.opsForValue().set(ACCESS_TOKEN_REDIS_KEY, JSONUtil.toJsonStr(token), Duration.ofSeconds(token.getExpireIn()));
        return token;
    }

    /**
     * 请求访问token
     * @return {@link TokenDto}
     */
    private TokenDto reqAccessToken() {
        Map<String,Object> params = new HashMap<>();
        params.put("app_id", xetConfig.getAppId());
        params.put("client_id", xetConfig.getClientId());
        params.put("secret_key", xetConfig.getSecretKey());
        params.put("grant_type", xetConfig.getGrantType());
        String respStr = HttpUtil.get(ACCESS_TOKEN_URL, params);
        log.info("【获取ACCESS TOKEN】===> {}",respStr);
        ResponseDto<TokenDto> responseDto;
        try {
            responseDto = JSONUtil.toBean(respStr, new TypeReference<ResponseDto<TokenDto>>() {
            },false);
        }catch (Exception e) {
            log.warn("【获取访问TOKEN失败】===> {}",e.getMessage());
            responseDto = null;
        }
        log.info("【同步Access TOKEN】");
        return responseDto == null ? null : responseDto.getData();
    }

    @Override
    public TokenDto get() {
        Boolean hasKey = stringRedisTemplate.hasKey(ACCESS_TOKEN_REDIS_KEY);
        TokenDto tokenDto = new TokenDto();
        AccessToken accessToken;
        // redis中token失效，或redis被清空
        // 查询数据库中的token是否过期，没有则拿出，有则重新获取
        if (hasKey == null || !hasKey) {
            List<AccessToken> accessTokens = accessTokenMapper.selectList(Wrappers.emptyWrapper());
            Optional<AccessToken> optionalAccessToken = accessTokens.stream().findFirst();
            if (optionalAccessToken.isPresent()) {
                accessToken = optionalAccessToken.get();
                // 小于100秒刷新token
                if (Instant.now().getEpochSecond() - accessToken.getSaveTime() > (accessToken.getExpireIn() - 100)) {
                    accessToken = syncAccessToken();
                }
            }else {
                accessToken = syncAccessToken();
            }
        }else {
            // cache exists
            String tokenStr = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_REDIS_KEY);
            accessToken = JSONUtil.toBean(tokenStr, AccessToken.class, true);
            Long expire = stringRedisTemplate.getExpire(ACCESS_TOKEN_REDIS_KEY);
            // 在有效期内刷新token
            if (expire != null && accessToken.getExpireIn() - expire > (accessToken.getExpireIn() - 100)) {
                accessToken = syncAccessToken();
            }
        }
        tokenDto.setAccessToken(accessToken.getToken());
        tokenDto.setExpiresIn(accessToken.getExpireIn());
        return tokenDto;
    }

    @Override
    public void cornSyncToken() {
        Boolean hasKey = stringRedisTemplate.hasKey(ACCESS_TOKEN_REDIS_KEY);
        if (hasKey == null || !hasKey) {
            syncAccessToken();
        }else {
            String tokenStr = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_REDIS_KEY);
            AccessToken accessToken = JSONUtil.toBean(tokenStr, AccessToken.class, true);
            Long expire = stringRedisTemplate.getExpire(ACCESS_TOKEN_REDIS_KEY);
            if (expire != null && accessToken.getExpireIn() - expire > (accessToken.getExpireIn() - 100)) {
                syncAccessToken();
            }
        }
    }

    @Override
    public List<AccessToken> list() {
        return accessTokenMapper.selectList(Wrappers.emptyWrapper());
    }
}
